from zope import interface
from zope.app.folder import Folder
from zope.app.cache.caching import getLocationForCache
from zope.app.cache.ram import RAMCache
from zope.exceptions.interfaces import UserError

from interfaces import IDBFormlibContent

class DBFormlibContent(Folder):
    """In order to use DBFormlibContent we need a subclass:
    that overrides some functions:
        
        >>> from zope.app.sqlscript import SQLScript
        >>> class TestClass(DBFormlibContent):
        ...     def _get_form_data(self, crud_id='', in_params={}):
        ...         return {'key':'value'}
        ...
        ...     def setup(self):
        ...         a = SQLScript(self.connectionName, 'sql', 'sqlArguments')
        ...         self['test'] = a
    
    setup function is for creation of necessary objects like SQLScripts.
    SQLScripts used by dbformlib should be put into SQLs dictionary.
    This allows dbformlib to update their connectionName parameter
    when it is changed.
    
    We are creating instance of TestClass and variables that are used
    as crud operation identifier (usually 'edit', 'add' etc.)
    
        >>> ckey = 'jan'
        >>> crud_id = 'edit'
        >>> obj = TestClass(connectionName='test_conn_name')
        
    Now we may get values from our object
    
        >>> print obj.get_values(crud_id=crud_id, ckey=ckey, use_cache=0, 
        ...                      in_params={'in':'val'})        
        {'key': 'value', 'in': 'val'}
    
    Data should be cached now
    
        >>> print obj._crud_data_cache.query(obj._get_key(ckey, crud_id),
        ...                                  default=None)
        {'key': 'value', 'in': 'val'}
        
    We can get data from cache with use_cache=1:
    
        >>> print obj.get_values(crud_id=crud_id, ckey=ckey, use_cache=1)
        {'key': 'value', 'in': 'val'}
        
    If we call this again with use_cache=0 then cache should be filled again
        
        >>> print obj.get_values(crud_id=crud_id, use_cache=0, ckey=ckey, 
        ...                      in_params={'in':'value2'})        
        {'key': 'value', 'in': 'value2'}
        
    And when we call with use_cache but with empty cache
        
        >>> obj = TestClass()
        >>> print obj.get_values(crud_id=crud_id, ckey=ckey, 
        ...                      in_params={'a':'b'}, use_cache=1)
        {'a': 'b', 'key': 'value'}
        
    """
    interface.implements(IDBFormlibContent)

    _use_cache_id = u'use_c'  # name of the param that says to 
                              #use values from cache
    
    def __init__(self, connectionName='', *args, **kw):
        super(DBFormlibContent, self).__init__(*args, **kw)
        self.connectionName = connectionName
        self._crud_data_cache = RAMCache()
        self.setup()
        
    def _get_key(self, ckey, crud_id):
        """ Returns unique key for cache
        """
        ukey = {'ckey' : ckey, 'crud_id':crud_id}
        loc = getLocationForCache(self) or '/'
        return loc, str(ukey)
        
    def get_use_cache_id(self):
        return self._use_cache_id
   
    def get_values(self, crud_id='', ckey='', in_params={}, use_cache=0):
        cache = self._crud_data_cache
        key = self._get_key(ckey, crud_id)
                
        if not use_cache:  # wyczyscic cache
            cache.invalidate(key)
        else:            
            # get from cache
            if cache and key[0]:
                _marker = object()
                result = cache.query(key, default=_marker)
                if result is not _marker:
                    return result
        
        result = {}
        # processing
        result.update(self._get_form_data(crud_id=crud_id, \
                                          in_params=in_params) or {})
        result.update(in_params)
        
        # store in cache
        if cache and key[0]:
            cache.set(result, key)
            
        return result
        
    def set_values(self, crud_id='', ckey='', data={}):
        """ Saves data by calling _set_form_data function.
            Also updates cache
        """
        # do not handle exceptions here
        cache = self._crud_data_cache
        errors = []        
        result = self._set_form_data(crud_id, data, errors)
        key = self._get_key(ckey, crud_id)
        if cache and key[0]:
            cache.set(data, key)
        return result, errors
    
    # THESE FUNCTIONS SHOULD BE OVERRIDEN
    def nextURL(self, crud_id=''):
        """ Used by add form, delete form and view form
            to redirect after 
        """
        raise UserError('Not implemented!')
        
    def setup(self):
        """ Create sql scripts here etc.
        """
        pass
        
    def _get_form_data(self, crud_id='', in_params={}):
        """ crud_id - identifier of the current form view
            in_params - dictionary containing entry field's values
        """
        raise UserError('Not implemented!')
    
    def _set_form_data(self, crud_id='', data={}, errors=[]):
        """ crud_id - identifier of current form
            data    - dictionary with form values
            errors  - list that should be filled with unicode strings
                      containing error description like u'Database error'
            
            Method should return results if any
        """
        raise UserError('Not implemented!')